/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.wider.foundation;

import com.wider.foundation.concurrency.MultiBuffer;
import com.wider.foundation.event.Event;
import com.wider.foundation.event.EventHub;
import com.wider.foundation.event.EventListener;
import java.util.Arrays;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

/**
 * This class acts as a Shim layer providing necessary abstraction for user to construct 
 * Buffered-Operations. Users of BufferingShim can easily bind all operation requests to
 * the underlying actions. The underlying actions will be triggered by either buffer-full
 * event or timer event.
 *
 * @author William Choi <avextk@gmail.com>
 */
public class BufferingShim implements Runnable, EventListener {
    public static int MAX_BUFF_DEPTH   = 256;
    public static int DEFAULT_BUFFERS   = 3;
    
    private MultiBuffer             tbuffers;
    private EventHub                eventhub;
    private Buffered                bufferen;
    private long                    timeoutv;
    private Timer                   timerobj;
    private int                     bufdepth;

    public BufferingShim(int buffer_length, long timeout_millis){
        this(DEFAULT_BUFFERS, buffer_length, timeout_millis);
    }

    public BufferingShim(int buffer_count, int buffer_length, long timeout_millis){
        if (buffer_length > MAX_BUFF_DEPTH){
            throw new IllegalArgumentException("Buffer Length exceeds the limit.");
        }
        
        bufdepth = buffer_length;
        timeoutv = timeout_millis;
        eventhub = new EventHub();
        tbuffers = new MultiBuffer(buffer_count, bufdepth);
        timerobj = null;
        bufferen = null;
        eventhub.addEventListener("TIMEOUT", this);
        eventhub.addEventListener("BUFFULL", this);
        eventhub.addEventListener("FLUSHIT", this);
    }

    public void shutdown() throws InterruptedException{
        cancelTimer();
        eventhub.shutdown();
    }
    
    public void flush(){
        eventhub.fire(new Event("FLUSHIT", eventhub, this));
    }

    public void bindTo(Buffered obj){
        bufferen = obj;
    }

    public void offer(Object...objs){
        if (objs.length < 1)
            return;

        List tbuff = tbuffers.getBuffer();

        if (tbuff.size() + objs.length > MAX_BUFF_DEPTH){
            throw new IllegalArgumentException("Exceeds the MAX buffer depth.");
        }

        int osize = tbuff.size();
            tbuff.addAll(Arrays.asList(objs));

        if (osize + objs.length >= bufdepth){
            eventhub.fire(new Event("BUFFULL", eventhub, tbuff));
        }
        else {
            //Buffer becomes non-empty, schedule a timer job for the timeout event
            if ((osize == 0) && (timeoutv > 0)) {
                final Runnable  containr;
                containr = this;
                timerobj = new Timer();
                timerobj.schedule(new TimerTask() {
                    @Override
                    public void run() {
                        containr.run();
                    }
                }, timeoutv);
            }
        }
    }

    @Override
    public void handleEvent(Event event) {
        //Since the Event source is this class as well, so ignore event mask checking

        //Get the ACTIVE buffer and lock it
        List tbuff = tbuffers.getBuffer();
        tbuffers.lockBuffer(tbuff);

        if (null != bufferen){
            bufferen.process(tbuff);
        }

        //Release the buffer and its content
        tbuff.clear();
        tbuffers.releaseBuffer(tbuff);
    }

    @Override
    public void run() {
        cancelTimer();
        eventhub.fire(new Event("TIMEOUT", eventhub, this));
    }

    private void cancelTimer(){
        if (null != timerobj){
            timerobj.cancel();
            timerobj = null;
        }
    }
}
